package cc.chinagps.gateway.client.packet;

import cc.chinagps.gateway.client.bean.DeviceInfo;
import cc.chinagps.gateway.client.bean.EncryptType;
import cc.chinagps.gateway.client.bean.TerminalType;
import cc.chinagps.gateway.client.packet.down.Packet01;
import cc.chinagps.gateway.client.packet.down.Packet02;
import cc.chinagps.gateway.common.Config;
import cc.chinagps.gateway.common.Constant;
import cc.chinagps.gateway.util.HexUtil;
import cc.chinagps.gateway.util.Sm2Util;
import cc.chinagps.gateway.util.XorUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import lombok.Data;
import lombok.ToString;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.encoders.Hex;

import java.nio.charset.StandardCharsets;

@Data
public abstract class AbstractPacket {
    protected byte start;
    protected byte msgId;
    protected String msgIdHex;
    protected byte infoType;
    protected String infoTypeHex;
    /**
     * 转发的非道的机械环保代码或者柴油车的vin
     */
    protected String vin;
    protected byte version;
    protected String terminalNo;
    protected byte encryptType;
    protected short msgBodyLength;
    @ToString.Exclude
    protected byte[] msgBody;
    protected ObdSignature obdSignature;
    protected byte xor;
    protected byte end;
    protected String source;
    protected Boolean signResult;
    @ToString.Exclude
    protected byte[] originalData;
    protected DeviceInfo deviceInfo;

    public void decode(ByteBuf byteBuf) throws Exception {
        TerminalType terminalType = TerminalType.find(deviceInfo.getTerminalType());
        int packetLen = byteBuf.readableBytes();
        source = HexUtil.clearBlank(ByteBufUtil.hexDump(byteBuf));
        start = byteBuf.readByte();
        if (start != 0x23) {
            throw new Exception("start flag error,source:" + source);
        }
        msgId = byteBuf.readByte();
        msgIdHex = String.format("0x%02x", msgId & 0xFF);
        byte[] xorBytes;
        switch (terminalType) {
            case ELECTRONIC_LABEL:
                xorBytes = new byte[packetLen - 1 - 1 - 1];
                byteBuf.getBytes(1, xorBytes);
                vin = byteBuf.readCharSequence(17, StandardCharsets.US_ASCII).toString();
                version = byteBuf.readByte();
                encryptType = byteBuf.readByte();
                msgBodyLength = byteBuf.readShort();
                msgBody = new byte[msgBodyLength];
                byteBuf.readBytes(msgBody);
                msgBody = decryptData(msgBody, encryptType);
                msgBodyLength = (short) msgBody.length;
                break;
            case OBD:
                xorBytes = new byte[packetLen - 1 - 66 - 1 - 1];
                byteBuf.getBytes(1, xorBytes);
                infoType = byteBuf.readByte();
                infoTypeHex = String.format("0x%02X", infoType & 0xFF);
                vin = byteBuf.readCharSequence(17, StandardCharsets.US_ASCII).toString();
                version = byteBuf.readByte();
                terminalNo = byteBuf.readCharSequence(20, StandardCharsets.US_ASCII).toString();
                encryptType = byteBuf.readByte();
                msgBodyLength = byteBuf.readShort();
                msgBody = new byte[msgBodyLength];
                byteBuf.readBytes(msgBody);
                obdSignature = new ObdSignature();
                obdSignature.decode(byteBuf);

                msgBody = decryptData(msgBody, encryptType);
                msgBodyLength = (short) msgBody.length;

                byte[] signature = ByteUtils.concatenate(obdSignature.getRBytes(), obdSignature.getSBytes());
                signResult = Sm2Util.verify(msgBody, deviceInfo.getPublicKey(), signature, Sm2Util.SM2_PID);
                break;
            default:
                throw new Exception("terminal type error,terminalType:" + terminalType);
        }
        xor = byteBuf.readByte();
        byte calXor = XorUtil.calXor(xorBytes);
        if (xor != calXor) {
            throw new Exception("pkg xor error,original xor:" + xor + ";computed xor:" + calXor + ";source:" + source);
        }
        end = byteBuf.readByte();
        if (end != 0x24) {
            throw new Exception("end flag error,source:" + source);
        }
        decodeData(msgBody);
    }

    public ByteBuf encode() throws Exception {
        TerminalType terminalType = TerminalType.find(deviceInfo.getTerminalType());
        ByteBuf byteBuf = Unpooled.buffer(25 + msgBody.length);
        byteBuf.writeByte(0x23);
        byteBuf.writeByte(msgId);
        byte[] vinBytes = vin.getBytes(Constant.DEFAULT_CHARSET);
        if (vinBytes.length != 17) {
            throw new Exception("envNo or vin length not equal 17,current len:" + vinBytes.length);
        }
        int xorIndex = 1;
        byte[] xorBytes;
        switch (terminalType) {
            case ELECTRONIC_LABEL:
                byteBuf.writeBytes(vinBytes);
                byteBuf.writeByte(version);
                if (msgId == 0x01 && Config.SYS.LOGIN_ENCRYPT) {
                    encryptType = EncryptType.SM2.type;
                }
                byteBuf.writeByte(encryptType);
                msgBody = encryptData(msgBody, encryptType);
                msgBodyLength = (short) msgBody.length;
                byteBuf.writeShort(msgBodyLength);
                byteBuf.writeBytes(msgBody);
                xorBytes = new byte[byteBuf.readableBytes() - xorIndex];
                byteBuf.getBytes(xorIndex, xorBytes);
                break;
            case OBD:
                byteBuf.writeByte(infoType);
                byteBuf.writeBytes(vinBytes);
                byteBuf.writeByte(version);
                byte[] terminalNoBytes = terminalNo.getBytes(StandardCharsets.UTF_8);
                if (terminalNoBytes.length != 20) {
                    throw new Exception("terminalNo length not equal 20,current len:" + terminalNoBytes.length);
                }
                byteBuf.writeBytes(terminalNoBytes);
                byteBuf.writeByte(encryptType);

                String sign = Sm2Util.sign(msgBody, deviceInfo.getPrivateKey(), Sm2Util.SM2_PID);
                byte[] signBytes = Hex.decode(sign);
                obdSignature = new ObdSignature();
                obdSignature.decode(signBytes);

                msgBody = encryptData(msgBody, encryptType);
                msgBodyLength = (short) msgBody.length;
                byteBuf.writeShort(msgBodyLength);
                byteBuf.writeBytes(msgBody);
                xorBytes = new byte[byteBuf.readableBytes() - xorIndex];
                byteBuf.getBytes(xorIndex, xorBytes);
                byteBuf.writeBytes(obdSignature.encode());
                break;
            default:
                throw new Exception("terminal type error,terminalType:" + terminalType);
        }
        byte xor = XorUtil.calXor(xorBytes);
        byteBuf.writeByte(xor);
        byteBuf.writeByte(0x24);
        return byteBuf;
    }

    public abstract void decodeData(byte[] data) throws Exception;

    public abstract byte[] encryptData(byte[] data, byte encryptType) throws Exception;

    public abstract byte[] decryptData(byte[] data, byte encryptType) throws Exception;

    public static void main(String[] args) throws Exception {
        String s = "2301ff4c565335353233363938373431353437362b53473030383938383235383431343735383739350300860414d05c47bf43924b3b48dfee8319fa2867384fc643401c0cf07568e0d7c5d96fe591790b433758e692c2df827eed3227c5a5c4a1ea47b48ee3e42cbdf839a8ad19234165966d131db5e077ee15a2e4e2592063993564310d541bdfa1d9e39218522432481cdd09f6e3b4bdc2a5edb822748de16b9b808327ffafd98d2f44b5bb2adf293ad8208b51314c1397781d25b375b34333fa64134a043b712aecd0f77edaa31526727d2045cfabe1a1f028181f8c736bc3a8531481d59cddce874b57446990435b3e1481ba24";
        s = "2301ff4c565335353233363938373431353437362a5347303038393838323538343134373538373935030086048095b92f14dcaa11ecf93cafa7344df38827615d01af58ffc31fbc8af2cf265778c0c5a6e483bd4f3b93663e9bfbf2ae5181c23d41421db478a74074905577985f8061873ede20fa82f58bceb5ae85fd4a977ebeeea3278ba012e2dff9478cd3d01c76a82523c770f019141d88a8de0d9977161e5334bfeec9d469844a8e10d4a44b4b9b54208e3f5beefe063b65d9c6fa9b5630fce9837923e410f977b50d6fb651b74be383204124d8ba92f04b9259210f1753d687e8a061b474c661fd51243dd7c8f731bfa59824";
        ByteBuf in = Unpooled.copiedBuffer(HexUtil.hexToBytes(s));
        DeviceInfo deviceInfo = new DeviceInfo();
        deviceInfo.setPublicKey("04e01b03ae165e9b0d4339c5b430c924a560e42848c9ac3bc6a5453a4595420ada9af605051ecceb06955b60c11a329ae83c690bf9b5260e4881ac0ee4471e8df4");
        deviceInfo.setPrivateKey("e2e219f40a508ebf04b73442a3ac557dfee5813da40fc346f9384ddd8f66a922");
        deviceInfo.setTerminalType(TerminalType.OBD.getType());
        BasePacket packet = new BasePacket(deviceInfo);
        packet.decode(in);
        System.out.println(packet);
        Packet01 packet01 = new Packet01(packet);
        packet01.decode();
        System.out.println(packet01);

        s = "2302024c565335353233363938373431353437362a53473030383938383235383431343735383739350300a604b2797eece93154d28cea689186071645eeaf05237f27b1815df59fe89b8eaf561d877ef7273025ea0990d3f7e54a524e2f967141bed1d190476d8888d9d786635a013347e6e6be29d9665930167c946508da9f7a04af5ca174e568c4ec12bad60561059cc2a4c6eeab43516b3a55b3b623796a713c60e257b7d60301f02896fbba124e56fbcb87c662b438c3927c562afebac030bd99788e4adf531c177a065fec68cd6b912040945ca465ca6b093befb17280856982406b8f20819029ba674a6a57010262222090d9ca4d08404b88d56995d28593baa92ab20015e4e74a259fb903aadfb1c5e60224";
        s = "2302014c565335353233363938373431353437362a53473030383938383235383431343735383739350300d004045387e5212f71531fd6cdf9471772089479e3e5695752e3a9a76b23d7d06e092fd3c7332fbdeceed4ae911dd7f9a0d93da632e74eb494b7d119a026c57c8a7be9766d68ca9db053af5ccef6a1c9f160b353475417d3d171f3ec3f57d7e576c3c8af25fb9e14831340dec0f2747dbfcffa86f7ba1689063e4fb5f7a32d85997ae2fb402655655d25d447f2e74ba76eae2df1c99aff386fd37bdc4c60248a69a20ed7ca09497592df5a7b7465c8737fa1889649426c376902b65cd2359c803dcc5cc059d68e5d6708edd9fc7cac106d20bddd7d7b71eacabe2ed381bf70331c4dcdff10ea82e7171316be6b19c4f0fbf120db9f514f9a9d89b05f3443a40254c8a5a76a8ab4098caf4e517d30a9862115ee4524";
        in = Unpooled.copiedBuffer(HexUtil.hexToBytes(s));
        packet = new BasePacket(deviceInfo);
        packet.decode(in);
        System.out.println(packet);
        Packet02 packet02 = new Packet02(packet);
        packet02.decode();
        System.out.println(packet02);
    }
}
